package com.cheetah.dispatcher.impl;

import com.cheetah.constants.AttributeKeyConstants;
import com.cheetah.constants.ProtocolConstants;
import com.cheetah.controller.protobuf.ProtobufHandlerManager;
import com.cheetah.dispatcher.Dispatcher;
import com.cheetah.dispatcher.RequestMapping;
import com.cheetah.exceptions.CheetahException;
import com.cheetah.exceptions.CheetahRuntimeException;
import com.cheetah.protocol.*;
import com.cheetah.protostuff.ProtostuffSerialization;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.stream.Stream;

/**
 * @ClassName ProtobuffDispatcher
 * @Description: Protobuff协议分发器
 * @Author Administrator
 * @Date 2019/7/19
 * @Version V1.0
 **/
@Component
@Slf4j
public class ProtobufDispatcher implements Dispatcher {

    @Autowired
    private ProtobufHandlerManager manager;

    @Override
    public void dispatch(ChannelHandlerContext ctx, Request request) throws Exception {
        if(request ==  null) {
            throw new CheetahException("Request is null!");
        }

        String type = (String)ctx.channel().attr(AttributeKey.valueOf(AttributeKeyConstants.ATTRIBUTE_KEY_TYPE)).get();
        //根据消息类型，利用java反射机制进行request的分发调用
        RequestMapping requestMapping = manager.requestMappings().get(type);

        Object[] params = generateParams(ctx, request, requestMapping);

        requestMapping.getExecutor().execute(() -> {
            try {
                invoke(ctx, request, type, requestMapping, params);
            }catch (Throwable e){
                ctx.fireExceptionCaught(e);
            }finally {
                ReferenceCountUtil.release(request);
            }
        });
    }

    /**
     * 设置invoke方法参数数组
     * @param ctx
     * @param request
     * @param requestMapping
     * @return
     */
    private Object[] generateParams(ChannelHandlerContext ctx, Request request, RequestMapping requestMapping) {
        Class[] parameterTypes = requestMapping.getMethod().getParameterTypes();
        Object[] params = new Object[parameterTypes.length];
        Stream.iterate(0, i -> i + 1).limit(parameterTypes.length).forEach(i -> {
            if(parameterTypes[i] == Request.class){
                params[i] = request;
            }else if(parameterTypes[i] == Channel.class){
                params[i] = ctx.channel();
            }
        });
        return params;
    }

    /**
     * handler 方法反射调用
     * @param ctx
     * @param request
     * @param type
     * @param requestMapping
     * @param params
     */
    private void invoke(ChannelHandlerContext ctx, Request request, String type, RequestMapping requestMapping, Object[] params){
        try {

            //反射调用至对应的controller层方法
            Response response = (Response)requestMapping.getMethod().invoke(requestMapping.getHandler(), params);

            CheetahMessage cheetahMessage = null;

            if(response != null){

                byte[] data = ProtostuffSerialization.serialize(response);

                cheetahMessage = CheetahMessage.builder()
                        .magic(ProtocolConstants.PROTOCOL_MAGIC_PROTO)
                        .version(ProtocolConstants.PROTOCOL_VERSION_PROTO)
                        .type(type)
                        .flag(ProtocolConstants.PROTOCOL_FlAG_PROTO_1)
//                        .headLen(0)
                        .bodyLen(data.length)
                        .data(data)
                        .build();

            }else { //没有返回值时添加body为空的对象,这里可以优化下反回空或者成功，节省字节
                cheetahMessage = CheetahMessage.builder()
                        .magic(ProtocolConstants.PROTOCOL_MAGIC_PROTO)
                        .version(ProtocolConstants.PROTOCOL_VERSION_PROTO)
                        .type(type)
                        .flag(ProtocolConstants.PROTOCOL_FlAG_PROTO_1)
//                        .headLen(0)
                        .bodyLen(0)
                        .build();
            }

            ctx.channel().writeAndFlush(cheetahMessage);
        } catch (Exception e) {
            throw new CheetahRuntimeException("request dispatch fail!",e);
        }
    }
}
